<?php
declare (strict_types = 1);

namespace yifang\console;

use Exception;
use InvalidArgumentException;
use LogicException;

use yifang\console\input\Argument;
use yifang\console\input\Definition;
use yifang\console\input\Option;

abstract class Command
{
    private $console;
    private $name;
    private $processTitle;
    private $aliases = [];
    private $definition;
    private $help;
    private $description;
    private $ignoreValidationErrors          = false;
    private $consoleDefinitionMerged         = false;
    private $consoleDefinitionMergedWithArgs = false;
    private $synopsis                        = [];
    private $usages                          = [];

    /** @var  Input */
    protected $input;

    /** @var  Output */
    protected $output;

    /**
     * 构造方法
     * @throws LogicException
     * @api
     */
    public function __construct()
    {
        $this->definition = new Definition();

        $this->configure();

        if (!$this->name) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
        }
    }

    /**
     * 配置指令
     */
    protected function configure()
    {
    }

    /**
     * 执行指令
     * @param Input  $input
     * @param Output $output
     */
    protected function execute(Input $input, Output $output)
    {
        return $this->handle();
    }

    /**
     * 执行
     * @return null
     */
    protected function handle(){
        return null;
    }

    /**
     * 忽略验证错误
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    /**
     * 设置控制台
     * @param Console $console
     */
    public function setConsole(Console $console = null)
    {
        $this->console = $console;
    }

    /**
     * 获取控制台
     * @return Console
     * @api
     */
    public function getConsole()
    {
        return $this->console;
    }

    /**
     * 添加参数
     * @param string $name        名称
     * @param int    $mode        类型
     * @param string $description 描述
     * @param mixed  $default     默认值
     * @return Command
     */
    public function addArgument($name,$mode = null, $description = '', $default = null)
    {
        $this->definition->addArgument(new Argument($name, $mode, $description, $default));
        return $this;
    }

    /**
     * 添加选项
     * @param string $name        选项名称
     * @param string $shortcut    别名
     * @param int    $mode        类型
     * @param string $description 描述
     * @param mixed  $default     默认值
     * @return Command
     */
    public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
    {
        $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
        return $this;
    }

    /**
     * 设置指令名称
     * @param string $name
     * @return Command
     */
    public function setName($name)
    {
        $this->validateName($name);
        $this->name = $name;
        return $this;
    }

    /**
     * 验证指令名称
     * @param string $name
     */
    private function validateName($name)
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
        }
    }

    /**
     * 设置进程名称
     * @param string $title The process title
     * @return $this
     */
    public function setProcessTitle($title)
    {
        $this->processTitle = $title;
        return $this;
    }

    /**
     * 获取指令名称
     * @return string
     */
    public function getName()
    {
        return $this->name ?: '';
    }

    /**
     * 设置描述
     * @param string $description
     * @return Command
     */
    public function setDescription($description)
    {
        $this->description = $description;
        return $this;
    }

    /**
     *  获取描述
     * @return string
     */
    public function getDescription()
    {
        return $this->description ?: '';
    }

    /**
     * 设置帮助信息
     * @param string $help
     * @return Command
     */
    public function setHelp($help)
    {
        $this->help = $help;
        return $this;
    }

    /**
     * 获取帮助信息
     * @return string
     */
    public function getHelp()
    {
        return $this->help ?: '';
    }

    /**
     * 设置参数定义
     * @param array|Definition $definition
     * @return Command
     * @api
     */
    public function setDefinition($definition)
    {
        if ($definition instanceof Definition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }
        $this->consoleDefinitionMerged = false;
        return $this;
    }

    /**
     * 获取参数定义
     * @return Definition
     * @api
     */
    public function getDefinition()
    {
        return $this->definition;
    }

    /**
     * 执行
     */
    public function run(Input $input, Output $output)
    {
        $this->input  = $input;
        $this->output = $output;

        $this->getSynopsis(true);
        $this->getSynopsis(false);

        $this->mergeConsoleDefinition();

        try {
            $input->bind($this->definition);
        } catch (Exception $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (function_exists('cli_set_process_title')) {
                if (false === @cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === PHP_OS) {

                    } else {
                        $error = error_get_last();
                        trigger_error($error['message'], E_USER_WARNING);
                    }
                }
            } elseif (function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            }
        }


        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        $input->validate();
        $statusCode = $this->execute($input, $output);
        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * 用户验证
     * @param Input  $input
     * @param Output $output
     */
    protected function interact(Input $input, Output $output)
    {
    }

    /**
     * 初始化
     */
    protected function initialize(Input $input, Output $output)
    {
    }

    /**
     * 合并参数定义
     * @param bool $mergeArgs
     */
    public function mergeConsoleDefinition($mergeArgs = true)
    {
        if (null === $this->console || (true === $this->consoleDefinitionMerged && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs))) {
            return;
        }
        if ($mergeArgs) {
            $currentArguments = $this->definition->getArguments();
            $this->definition->setArguments($this->console->getDefinition()->getArguments());
            $this->definition->addArguments($currentArguments);
        }
        $this->definition->addOptions($this->console->getDefinition()->getOptions());
        $this->consoleDefinitionMerged = true;
        if ($mergeArgs) {
            $this->consoleDefinitionMergedWithArgs = true;
        }
    }

    /**
     * 获取简介
     * @param bool $short 是否简单的
     * @return string
     */
    public function getSynopsis($short = false)
    {
        $key = $short ? 'short' : 'long';
        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }
        return $this->synopsis[$key];
    }
}

